﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SmartMathLibrary;

namespace SmartMathLibrary.LinearEquationSolvers
{
    /// <summary>
    /// This class provides methods to solve a linear system by using the Jacobi method.
    /// </summary>
    [Serializable]
    public class JacobiLinearSystemSolver : AbstractRealSplittingMethod
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="JacobiLinearSystemSolver"/> class.
        /// </summary>
        /// <param name="coefficientMatrix">The coefficient matrix to solve.</param>
        /// <param name="resultVector">The result vector of the equation system to solve.</param>
        public JacobiLinearSystemSolver(Matrix coefficientMatrix, GeneralVector resultVector)
            : base(coefficientMatrix, resultVector)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="JacobiLinearSystemSolver"/> class.
        /// </summary>
        /// <param name="equationSystem">The system of equation to solve.</param>
        public JacobiLinearSystemSolver(LinearEquationSystem equationSystem)
            : base(equationSystem)
        {
        }

        /// <summary>
        /// Solves the linear system, which is defined by the coefficient matrix by using the
        /// Jacobi method. Check if the system of equations converge, otherwise the calculated
        /// results are wrong.
        /// </summary>
        /// <returns>
        /// The solved linear system in form of a vector.
        /// </returns>
        public override GeneralVector SolveLinearSystem()
        {
            return this.SolveLinearSystem(new GeneralVector(this.CoefficientMatrix.Rows), 100, 1e-15);
        }

        /// <summary>
        /// Solves the linear system, which is defined by the coefficient matrix by using the
        /// Jacobi method. Check if the system of equations converge, otherwise the calculated
        /// results are wrong.
        /// </summary>
        /// <param name="startValues">The start values of the calculation in form of a vector.</param>
        /// <returns>
        /// The solved linear system in form of a vector.
        /// </returns>
        public override GeneralVector SolveLinearSystem(GeneralVector startValues)
        {
            return this.SolveLinearSystem(startValues, 100, 1e-15);
        }

        /// <summary>
        /// Solves the linear system, which is defined by the coefficient matrix by using the
        /// Jacobi method. Check if the system of equations converge, otherwise the calculated
        /// results are wrong.
        /// </summary>
        /// <param name="startValues">The start values of the calculation in form of a vector.</param>
        /// <param name="maxIterations">The maximum of iterations for the calculation.</param>
        /// <returns>
        /// The solved linear system in form of a vector.
        /// </returns>
        public override GeneralVector SolveLinearSystem(GeneralVector startValues, int maxIterations)
        {
            return this.SolveLinearSystem(startValues, maxIterations, 1e-15);
        }

        /// <summary>
        /// Solves the linear system, which is defined by the coefficient matrix by using the 
        /// Jacobi method. Check if the system of equations converge, otherwise the calculated
        /// results are wrong.
        /// </summary>
        /// <param name="startValues">The start values of the calculation in form of a vector.</param>
        /// <param name="maxIterations">The maximum of iterations for the calculation.</param>
        /// <param name="precision">The precision of the results to calculate.</param>
        /// <returns>The solved linear system in form of a vector.</returns>
        public override GeneralVector SolveLinearSystem(GeneralVector startValues, int maxIterations, double precision)
        {
            GeneralVector workingValues = startValues.Copy();

            if (this.CoefficientMatrix.Columns < 2)
            {
                throw new ArithmeticException("The coefficient matrix must have a minimum of two columns.");
            }

            if (this.CoefficientMatrix.Rows != workingValues.Count)
            {
                throw new ArithmeticException(
                    "The number of matrix rows has to be even with the number of vector rows, which defines the start values.");
            }

            int iterationCount = 0;
            GeneralVector precisionChecking = new GeneralVector(this.CoefficientMatrix.Rows);

            for (int i = 0; i < precisionChecking.Count; i++)
            {
                precisionChecking[i] = double.MinValue;
            }

            while ((iterationCount < maxIterations) &&
                   (!this.PrecisionCheck(workingValues, precisionChecking, precision)))
            {
                for (int i = 0; i < this.CoefficientMatrix.Rows; i++)
                {
                    double tempuri = 0;

                    for (int j = 0; j < this.CoefficientMatrix.Columns; j++)
                    {
                        if (i != j)
                        {
                            tempuri += this.CoefficientMatrix.GetValueAtPosition(new MatrixPosition(i, j)) *
                                       workingValues[j];
                        }
                    }

                    precisionChecking[i] = workingValues[i];
                    workingValues[i] = (this.ResultVector[i] - tempuri)
                                       / this.CoefficientMatrix.GetValueAtPosition(new MatrixPosition(i, i));
                }

                iterationCount++;
            }

            return workingValues;
        }
    }
}